/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt.make;

import com.intellij.gwt.GwtBundle;
import com.intellij.gwt.GwtConfiguration;
import com.intellij.gwt.module.GwtModulesManager;
import com.intellij.gwt.module.model.GwtModule;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootsTraversing;
import com.intellij.openapi.util.MultiValuesMap;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.util.PathsList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.DataInputStream;
import java.util.ArrayList;
import java.util.List;

public class GwtCompiler implements ProjectComponent, ClassInstrumentingCompiler {
  private static final Logger LOG = Logger.getInstance("#com.intellij.gwt.make.GwtCompiler");
  private Project myProject;
  private GwtModulesManager myGwtModulesManager;

  public GwtCompiler(Project project, GwtModulesManager modulesManager) {
    myProject = project;
    myGwtModulesManager = modulesManager;
  }

  @NotNull
  public String getDescription() {
    return GwtBundle.message("compiler.description.google.compiler");
  }

  public boolean validateConfiguration(CompileScope scope) {
    return true;
  }

  public ValidityState createValidityState(DataInputStream is) throws IOException {
    return new GwtItemValidityState(is);
  }

  @NotNull
  public ProcessingItem[] getProcessingItems(final CompileContext context) {
    if (!GwtConfiguration.getInstance().isValidConfiguration()) return ProcessingItem.EMPTY_ARRAY;

    final ArrayList<ProcessingItem> result = new ArrayList<ProcessingItem>();
    ApplicationManager.getApplication().runReadAction(new Runnable() {
      public void run() {
        final GwtModule[] modules = myGwtModulesManager.getGwtModules();
        for (GwtModule module : modules) {
          if (myGwtModulesManager.isLibraryModule(module)) continue;

          for (VirtualFile file : module.getPublicRoots()) {
            addFilesRecursively(module, file, result);
          }
          for (VirtualFile file : module.getSourceRoots()) {
            addFilesRecursively(module, file, result);
          }
          addFilesRecursively(module, module.getModuleFile(), result);
        }
      }
    });
    return result.toArray(new ProcessingItem[result.size()]);
  }

  private static void addFilesRecursively(final GwtModule module, final VirtualFile file, final List<ProcessingItem> result) {
    if (file.isDirectory()) {
      final VirtualFile[] children = file.getChildren();
      for (VirtualFile child : children) {
        addFilesRecursively(module, child, result);
      }
    }
    else {
      result.add(new GwtModuleFileProcessingItem(module, file));
    }
  }

  public ProcessingItem[] process(final CompileContext context, ProcessingItem[] items) {
    if (!GwtConfiguration.getInstance().isValidConfiguration()) return ProcessingItem.EMPTY_ARRAY;

    MultiValuesMap<GwtModule, GwtModuleFileProcessingItem> module2Items = new MultiValuesMap<GwtModule, GwtModuleFileProcessingItem>();
    for (ProcessingItem item : items) {
      final GwtModuleFileProcessingItem processingItem = (GwtModuleFileProcessingItem)item;
      module2Items.put(processingItem.getModule(), processingItem);
    }

    final ArrayList<ProcessingItem> compiled = new ArrayList<ProcessingItem>();

    for (GwtModule module : module2Items.keySet()) {
      if (compile(context, module)) {
        compiled.addAll(module2Items.get(module));
      }
    }

    return compiled.toArray(new ProcessingItem[compiled.size()]);
  }

  private static boolean compile(final CompileContext context, final GwtModule gwtModule) {
    final Module module = new ReadAction<Module>() {
      protected void run(final Result<Module> result) {
        result.setResult(gwtModule.getModule());
      }
    }.execute().getResultObject();
    final File outputDir = GwtCompilerPaths.getOutputDirectory(module);
    final File generatedDir = GwtCompilerPaths.getDirectoryForGenerated(module);
    generatedDir.mkdirs();
    outputDir.mkdirs();


    @NonNls List<String> command = new ReadAction<List<String>>() {
      protected void run(final Result<List<String>> result) {
        result.setResult(createCommand(gwtModule, outputDir, generatedDir));
      }
    }.execute().getResultObject();

    final ProcessBuilder pBuilder = new ProcessBuilder(command);
    context.getProgressIndicator().setText2(GwtBundle.message("progress.text.compiling.gwt.module.0", gwtModule.getQualifiedName()));

    pBuilder.directory(outputDir);

    try {
      GwtCompilerProcessHandler handler = new GwtCompilerProcessHandler(pBuilder, context, gwtModule.getModuleFile().getUrl());
      handler.startNotify();
      handler.waitFor();
    }
    catch (IOException e) {
      context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
      return false;
    }

    final ArrayList<File> filesToRefresh = new ArrayList<File>();
    addOutputItems(outputDir, filesToRefresh);
    CompilerUtil.refreshIOFiles(filesToRefresh);

    return context.getMessageCount(CompilerMessageCategory.ERROR) == 0;
  }

  private static void addOutputItems(final File file, final List<File> filesToRefresh) {
    if (file.isDirectory()) {
      final File[] children = file.listFiles();
      if (children != null)  {
        for (File child : children) {
          addOutputItems(child, filesToRefresh);
        }
      }
    }
    else {
      filesToRefresh.add(file);
    }
  }

  private static List<String> createCommand(final GwtModule module, final File outputDir, final File generatedDir) {
    ModuleRootManager rootManager = ModuleRootManager.getInstance(module.getModule());
    final ProjectJdk projectJdk = rootManager.getJdk();
    LOG.assertTrue(projectJdk != null);

    @NonNls List<String> command = new ArrayList<String>();
    command.add(projectJdk.getVMExecutablePath());
    command.add("-classpath");
    command.add(createClasspath(module.getModule(), projectJdk));
    command.add("com.google.gwt.dev.GWTCompiler");
    command.add("-logLevel");
    command.add("TRACE");
    command.add("-out");
    command.add("\"" + outputDir.getAbsolutePath() + "\"");
    command.add("-gen");
    command.add("\"" + generatedDir.getAbsolutePath() + "\"");
    command.add("-style");
    command.add(GwtConfiguration.getInstance().getOutputStyle().getId());
    command.add(module.getQualifiedName());
    return command;
  }

  private static String createClasspath(Module module, final ProjectJdk jdk) {
    final PathsList classPath = new PathsList();
    ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
    for (VirtualFile src : rootManager.getSourceRoots()) {
      classPath.add(src);
    }
    final ProjectRootsTraversing.RootTraversePolicy policy = new ProjectRootsTraversing.RootTraversePolicy(null, ProjectRootsTraversing.RootTraversePolicy.ADD_CLASSES,
                                                                                                           ProjectRootsTraversing.RootTraversePolicy.ADD_CLASSES,
                                                                                                           ProjectRootsTraversing.RootTraversePolicy.RECURSIVE);
    ProjectRootsTraversing.collectRoots(module, policy, classPath);

    classPath.add(GwtConfiguration.getInstance().getDevJarPath());

    return "\"" + classPath.getPathsString() + "\"";
  }

  public void initComponent() {
  }

  public void disposeComponent() {
  }

  @NotNull
  public String getComponentName() {
    return "GWT Compiler";
  }

  public void projectOpened() {
    CompilerManager.getInstance(myProject).addCompiler(this);
  }

  public void projectClosed() {
    CompilerManager.getInstance(myProject).addCompiler(this);
  }

}
